package com.yeziji.utils;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.yeziji.constant.CacheStrategy;
import lombok.extern.slf4j.Slf4j;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 本地缓存工具
 *
 * @author hwy
 * @since 2024/07/09 21:03
 **/
@Slf4j
public class LocalCacheUtils {
    /**
     * local cache null
     */
    public static final String LOCAL_NULL_TAG = "lcn";
    public static final String CALL_NULL_TAG = "empty";
    /**
     * 默认锁时间(ms)
     */
    public static final long DEFAULT_LOCK_TIME = 10;
    /**
     * 快速缓存, 用于搭配 redis 兼容并发处理
     */
    private static final Cache<String, Object> LOCAL_FAST_CACHE =
            CacheBuilder.newBuilder()
                    // 超过 1000 -> LRU
                    .maximumSize(1000)
                    // 内存开始不足时开始回收
                    .softValues()
                    // 30秒后再读就视为过期(本地缓存主要用于分担并发, 所以这里时长不需要弄太长, 否则要考虑缓存同步问题)
                    .expireAfterWrite(30, TimeUnit.SECONDS)
                    // 允许并发缓存 16 个线程
                    .concurrencyLevel(16)
                    .build();
    /**
     * 非公平锁
     */
    private static final Lock REENTRANT_LOCK = new ReentrantLock(false);

    /**
     * 删除缓存
     *
     * @param key 缓存键值
     * @return {@link Boolean}
     */
    public static boolean del(String key) {
        try {
            LOCAL_FAST_CACHE.invalidate(key);
        } catch (Exception e) {
            log.warn("del [{}] local cache error: {}", key, e.getMessage());
            return false;
        }
        return true;
    }

    /**
     * 获取本地缓存
     * <p>
     * 这里如果为 null 会返回 null 标识, 而不会返回 null
     * </p>
     *
     * @param key               缓存键值
     * @param cacheStrategy 缓存策略
     * @return {@link Object} 获取结果
     */
    public static Object get(String key, CacheStrategy cacheStrategy, Callable<Object> loader) {
        switch (cacheStrategy) {
            case NOT_LOCK:
                Object data = LOCAL_FAST_CACHE.getIfPresent(key);
                if (data == null) {
                    Object loadData = execLoader(loader);
                    LOCAL_FAST_CACHE.put(key, loadData);
                    return loadData;
                }
                return data;
            case TRY_LOCK:
                return tryGet(key, DEFAULT_LOCK_TIME, loader);
            // 非指定策略，走本地
            default:
                return execLoader(loader);
        }
    }

    /**
     * 该方法会获取到 null
     *
     * @param key               键值
     * @param cacheStrategy 缓存策略
     * @return {@link Object} 任意对象
     */
    public static Object getIfPresent(String key, CacheStrategy cacheStrategy) {
        Object data = get(key, cacheStrategy, null);
        if (isNullTag(data)) {
            return null;
        }
        return data;
    }

    /**
     * tryLock then get
     * <p>可以一定程度上防止穿透</p>
     *
     * @param key      键值
     * @param lockTime lock 时间
     * @param loader   加载方法
     * @return {@link Object}
     */
    public static Object tryGet(String key, Long lockTime, Callable<Object> loader) {
        try {
            // 单机锁避免击穿, 默认 10 毫秒是内存获取速度, 如果获取不到就走 loader 方法;
            // FIXME: 这个值要根据实际服务器测压命中情况做调整, 最好做到 redis 与 local cache 对半开; 或者 local cache 占用比例更大
            if (REENTRANT_LOCK.tryLock(Optional.ofNullable(lockTime).orElse(DEFAULT_LOCK_TIME), TimeUnit.MILLISECONDS)) {
                try {
                    // maybe null?: FIXME:这个值是有价值的，所以考虑下来感觉不应该做 null 处理
//                    if (isNullTag(data)) {
//                        return null;
//                    }
                    // if null return null tag
                    return LOCAL_FAST_CACHE.get(key, () -> execLoader(loader));
                } catch (Exception e) {
                    return execLoader(loader);
                } finally {
                    REENTRANT_LOCK.unlock();
                }
            }
        } catch (InterruptedException e) {
            log.error("get [{}] lock interrupt then load redis data", key);
            return execLoader(loader);
        }
        // debug: 根据日志调试命中情况
        log.info("get [{}] local cache tryLock failed !!!", key);
        return execLoader(loader);
    }

    /**
     * 执行 loader
     * <p>
     * 如果 loader == null 那么就会返回 {@link #CALL_NULL_TAG} 否则，会返回 {@link Callable#call()} 的运行结果(如 call 命中是 empty tag 标签，那么本地缓存视为空)
     * </p>
     *
     * @param loader 执行 callable
     * @return {@link Object}
     */
    private static Object execLoader(Callable<Object> loader) {
        return Optional.ofNullable(loader).map(callable -> {
            try {
                // check call is null ?
                Object call = callable.call();
                if (isNullTag(call)) {
                    call = CALL_NULL_TAG;
                }
                return call;
            } catch (Exception e) {
                // exception return null tag
                log.warn("exec loader: {}", e.getMessage());
                return CALL_NULL_TAG;
            }
        }).orElse(CALL_NULL_TAG);
    }

    /**
     * 判断是否为 null 标识
     *
     * @param value 判断对象
     * @return {@link Boolean}
     */
    public static boolean isNullTag(Object value) {
        if (value == null) {
            return true;
        }
        return value instanceof String && (Objects.equals(value, LOCAL_NULL_TAG) || Objects.equals(value, CALL_NULL_TAG));
    }

    /**
     * 提高给外部调用
     */
        public static Cache<String, Object> getFastCache() {
        return LOCAL_FAST_CACHE;
    }
}
